use app::*;
use axum::{
    async_trait, extract::{ FromRef, FromRequestParts, Host, Query, State}, http::{header::{HeaderMap, SET_COOKIE}, request::Parts, StatusCode}, response::{IntoResponse, Json, Redirect, Response}, Form, RequestPartsExt
};

use axum::{
    error_handling::HandleErrorLayer,
    extract::DefaultBodyLimit,    
    response::Html,
    body::Body,
    http::{Request, Uri},
    routing::{get, post, Router},
};
use axum::response::Result as AResult;

use serde::{Deserialize, Serialize, *};
use fileserv::file_and_error_handler;
use leptos::*;
use leptos_axum::{generate_route_list, LeptosRoutes};
use std::path::{self, PathBuf};
use tower_http::services::ServeDir;
use tower::{BoxError, ServiceBuilder};
use tower_http::limit::RequestBodyLimitLayer;
use tower_http::trace::TraceLayer;
use std::time::Duration;
use std::borrow::Cow;
use std::sync::Arc;
use tokio;
use tokio::signal;
use tower::ServiceExt;

use anyhow::{bail, Result};
type SharedHandle = Arc<Shared>;
use chrono::prelude::*;

pub mod fileserv;

pub struct Shared {    
    pub timestamp : std::sync::RwLock<String>,
    pub site_root: String,
}
    
#[derive(Deserialize, Debug, Default)]
 pub struct SearchParams {
    //  #[serde(default, deserialize_with = "empty_string_as_none")]
    //#[serde(default = "default_string")]
   pub query: String,   
}

#[tokio::main]
async fn main() {
    simple_logger::init_with_level(log::Level::Debug).expect("couldn't initialize logging");

    // Setting get_configuration(None) means we'll be using cargo-leptos's env values
    // For deployment these variables are:
    // <https://github.com/leptos-rs/start-axum#executing-a-server-on-a-remote-machine-without-the-toolchain>
    // Alternately a file can be specified such as Some("Cargo.toml")
    // The file would need to be included with the executable when moved to deployment
    
    let mut conf = get_configuration(None).await.unwrap();
    conf.leptos_options.output_name = "start-axum".to_owned();
    // 这个设置是因为通常在项目目录下之下cargo leptos watch, 所以./target/site将能很好的支持cargo leptos watch，
    // 如果真实release，则建议用配置文件实现！！！！ 
    conf.leptos_options.site_root = "./target/site".to_owned();
    conf.leptos_options.site_pkg_dir = "site/pkg".to_owned();
    
println!("{:#?}", conf);

    let port = 3000;
    let leptos_options = conf.leptos_options;
    let addr =   format!("0.0.0.0:{}", port);  //   leptos_options.site_addr;
    let routes = generate_route_list(App);

    let assets_dir = PathBuf::from(leptos_options.site_root.as_str());
    let shared: Arc<Shared> = Arc::new(
        Shared{
            timestamp: std::sync::RwLock::new(String::from("0")),
            site_root: leptos_options.site_root.clone(),
        }
    );

    let app_state = leptos_options.clone();

    // build our application with a route
    let app = Router::new()    
    // 特别注意，axum的路由等功能注册，都必须在leptos_axum之前，否则会报错！！！
        .route("/post_hello", post(post_todo))
        .route("/get_hello", get(get_hello))
        .route("/favicon.ico", get( spec_res_handler) )
        .with_state(Arc::clone(&shared))
        .layer(
            ServiceBuilder::new()
                // Handle errors from middleware
                .layer(HandleErrorLayer::new(handle_error))
                .load_shed()
                .concurrency_limit(1024)
                .timeout(Duration::from_secs(5))
                .layer(TraceLayer::new_for_http())
                .into_inner(),
        )
        .layer(DefaultBodyLimit::disable())
        .layer(RequestBodyLimitLayer::new(
            10 * 1024 * 1024, /* 250mb */
        ))              
        .nest_service("/site", ServeDir::new(assets_dir))    
        // 此处之下，才是leptos_axum注册的！！！
        //.leptos_routes(&leptos_options, routes, App)    
	.leptos_routes_with_context(
            &leptos_options,
            routes,
            {  let app_state = app_state.clone();
                move || provide_context(app_state.clone())
            },
            App,
        )
        .fallback(file_and_error_handler)  
        .with_state(leptos_options);       

    // run our app with hyper
    // `axum::Server` is a re-export of `hyper::Server`
    log::info!("listening on http://{}", &addr);
    let listener = tokio::net::TcpListener::bind(&addr).await.unwrap();
     
    /*  
    axum::serve(listener, app.into_make_service())
        .await
        .unwrap();    
    */ 
    let r = axum::serve(listener, app.into_make_service())
        // 因为被leptos 封装，这里的with_graceful_shutdown实际并不起作用！！！！
        .with_graceful_shutdown(shutdown_signal("auxum server"))
        .await;

    if let std::result::Result::Err(err) = r {
        println!("{:#?}", err);
        //  bail!("create auxum web server failed")
    } 
    // Ok(())
}

async fn shutdown_signal(name: &str) {
    let ctrl_c = async {
        signal::ctrl_c()
            .await
            .expect("failed to install Ctrl+C handler");
    };

    #[cfg(unix)]
    let terminate = async {
        signal::unix::signal(signal::unix::SignalKind::terminate())
            .expect("failed to install signal handler")
            .recv()
            .await;
    };

    #[cfg(not(unix))]
    let terminate = std::future::pending::<()>();

    tokio::select! {
        _ = ctrl_c => {},
        _ = terminate => {},
    }   
}


async fn handle_error(error: BoxError) -> impl IntoResponse {
    if error.is::<tower::timeout::error::Elapsed>() {
        return (
            StatusCode::REQUEST_TIMEOUT,
            Cow::from("request timed out  [ handle_error ]"),
        );
    }

    if error.is::<tower::load_shed::error::Overloaded>() {
        return (
            StatusCode::SERVICE_UNAVAILABLE,
            Cow::from("service is overloaded, try again later  [ handle_error ]"),
        );
    }

    (
        StatusCode::INTERNAL_SERVER_ERROR,
        Cow::from(format!(
            "!!!!!!!!!! Unhandled internal error: {} [ handle_error ]",
            error
        )),
    )
}

async fn spec_res_handler(  uri: Uri, State(shared): State<SharedHandle>,) ->  AResult<Response> {
    logging::log!("favicon_handler: {}/{}", shared.site_root, uri);
    let site =  path::PathBuf::from(&shared.site_root);    
    let req = Request::builder()
        .uri(uri.clone())
        .body(Body::empty())
        .unwrap();
    let res = ServeDir::new(&site).oneshot(req).await?;
    return Ok(res.map(Body::new).into_response());
}


async fn get_hello(State(shared): State<SharedHandle>, Query(q): Query<SearchParams>) ->  AResult<Response> {
    let r = shared.timestamp.read().unwrap();
    let s = r.to_string();
    logging::log!("get_hello: {:?}", s);

    return Ok( Html(format!(
             r#"<html><head><h1 align=center><Warnning</h1></head><body><BR><BR>
             <div align=center><span style="color:red">GET不建议在axum中实现，请把界面构成都交给leptos处理，以保证一致风格！</span></div>
             <BR>
             <div> Input is : {:?}</div>
             <BR>
             <div> shared.timestamp = {:?}</div>
             </body>
             </html>"#,
            q, s
         )).into_response() )      
 }
 pub async fn post_todo(State(shared): State<SharedHandle>, Form(q): Form<SearchParams>, )  -> AResult<Response> {  

    let now = Local::now().format("%Y-%m-%d %H:%M:%S").to_string();
    log::info!("{:?}, {:?}", q, &now);  
    let mut w = shared.timestamp.write().unwrap();
    *w = now;
    
    return Ok(Redirect::to("/").into_response());
}

///////


